﻿using GrapeCity.Spread.Sheets.ExcelIO;
using GrapeCity.Windows.SpreadSheet.Data;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
namespace GrapeCity.ExcelService.Controllers
{
    #region ExportController
    
    /// <summary>
    ///  Represent the export API which will export data to file with specified format.
    /// </summary>
    public class ExportController : ApiController
    {
        // POST api/export
        /// <summary>
        ///  Post the data to server and download the exported file with specified format.
        /// </summary>
        public async Task<HttpResponseMessage> Post()
        {
            SpreadExportParam exportParam = GetExportParm();
            MemoryStream stream = new MemoryStream();
            Exporter exporter = new Exporter(exportParam.Spread);
            this.export(exporter, exportParam, stream);
            stream.Seek(0, SeekOrigin.Begin);
            string mediaType = MimeMap[exportParam.ActualExportFileType];

            var result = new HttpResponseMessage(HttpStatusCode.OK);
            result.Content = new StreamContent(stream);
            result.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
            //we used attachment to force download
            result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = exportParam.ActualExportFileName;
            return result;
        }

        private SpreadExportParam GetExportParm()
        {
            SpreadExportParam exportParam;
            Request.Content.ReadAsStreamAsync().Result.Position = 0;
            var formData = Request.Content.ReadAsFormDataAsync().Result;
            string data = formData.Get("data");
            var bytes = System.Text.Encoding.UTF8.GetBytes(data);
            MemoryStream stream = new MemoryStream(bytes);

            using (JsonTextReader reader = new JsonTextReader(new StreamReader(stream)))
            {
                exportParam = JsonSerializer.Create(new JsonSerializerSettings()).Deserialize<SpreadExportParam>(reader);
            }
            stream.Dispose();
            if (exportParam == null || exportParam.Spread == null)
            {
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }
            return exportParam;
        }

        private void export(Exporter exporter, SpreadExportParam exportParam, Stream stream)
        {
            ExportFileType exportFileType = exportParam.ActualExportFileType;
            switch (exportFileType)
            {
                case ExportFileType.Xlsx:
                case ExportFileType.Xls:
                    exportExcel(exporter, exportParam.ExcelParam, stream, exportFileType == ExportFileType.Xlsx ? ExcelFileFormat.XLSX : ExcelFileFormat.XLS);
                    break;
                case ExportFileType.Csv:
                    exportCsv(exporter, exportParam.CsvParam, stream);
                    break;
                case ExportFileType.Text:
                    exportText(exporter, exportParam.TextParam, stream);
                    break;
                case ExportFileType.Pdf:
                    exportPdf(exporter, exportParam.PdfParam, stream);
                    break;
                case ExportFileType.Html:
                    exportHtml(exporter, exportParam.HtmlParam, stream);
                    break;
                default:
                    break;
            }            
        }


        private void exportExcel(Exporter exporter, ExcelParam excelParam, Stream stream, ExcelFileFormat fileFormat)
        {
            var excelSaveFlags = ExcelSaveFlags.NoFlagsSet;
            var password = string.Empty;
            if (excelParam != null)
            {
                excelSaveFlags = excelParam.saveFlags;
                password = excelParam.password;
            }
            exporter.SaveExcel(stream, fileFormat, excelSaveFlags, password);
        }

        private void exportPdf(Exporter exporter, PdfParam pdfParam, Stream stream)
        {
            int[] sheets = null;
            PdfExportSettings setting = null;
            if (pdfParam != null)
            {
                sheets = pdfParam.sheetIndexes;
                setting = pdfParam.setting;
            }
            exporter.SavePdf(stream, setting, sheets);
        }

        private void exportCsv(Exporter exporter, CsvParam csvParam, Stream stream)
        {
            int sheetIndex = -1;
            var sheetOnlyParam = csvParam as SheetOnlyParam;
            if (sheetOnlyParam != null)
            {
                sheetIndex = sheetOnlyParam.sheetIndex.HasValue ? sheetOnlyParam.sheetIndex.Value : -1;
            }

            var textFileSaveFlags = TextFileSaveFlags.None;
            Encoding encoding = null;
            var plainTextParam = csvParam as PlainTextParam;
            if (plainTextParam != null)
            {
                textFileSaveFlags = plainTextParam.saveFlags;
                try
                {
                    encoding = Encoding.GetEncoding(plainTextParam.encoding);
                }
                catch { }
                if (encoding == null)
                {
                    encoding = Encoding.UTF8;
                }
            }

            if (csvParam == null)
            {
                exporter.SaveCsv(stream, sheetIndex, textFileSaveFlags, Encoding.UTF8);
                return;
            }

            var row = csvParam.row.HasValue ? csvParam.row.Value : 0;
            var col = csvParam.column.HasValue ? csvParam.column.Value : 0;
            var rowCount = csvParam.rowCount.HasValue ? csvParam.rowCount.Value : -1;
            var colCount = csvParam.columnCount.HasValue ? csvParam.columnCount.Value : -1;

            exporter.SaveTextFileRange(stream, sheetIndex, row, col, rowCount, colCount, textFileSaveFlags, csvParam.rowDelimiter, csvParam.columnDelimiter, csvParam.cellDelimiter, encoding);

        }
        private void exportText(Exporter exporter, TextParam textParam, Stream stream)
        {
            int sheetIndex = -1;
            var sheetOnlyParam = textParam as SheetOnlyParam;
            if (sheetOnlyParam != null)
            {
                sheetIndex = sheetOnlyParam.sheetIndex.HasValue ? sheetOnlyParam.sheetIndex.Value : -1;
            }

            var row = 0;
            var col = 0;
            var rowCount = -1;
            var colCount = -1;
            var rowDelimiter = "\r\n";
            var columnDelimiter = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
            var cellDelimiter = "\"";
            if (textParam != null)
            {
                row = textParam.row;
                col = textParam.column;
                rowCount = textParam.rowCount;
                colCount = textParam.columnCount;
                rowDelimiter = textParam.rowDelimiter;
                columnDelimiter = textParam.columnDelimiter;
                cellDelimiter = textParam.cellDelimiter;
            }

            var textFileSaveFlags = TextFileSaveFlags.None;
            Encoding encoding = null;
            var plainTextParam = textParam as PlainTextParam;
            if (plainTextParam != null)
            {
                textFileSaveFlags = plainTextParam.saveFlags;
                try
                {
                    encoding = Encoding.GetEncoding(plainTextParam.encoding);
                }
                catch { }
                if (encoding == null)
                {
                    encoding = Encoding.UTF8;
                }
            }
            exporter.SaveTextFileRange(stream, sheetIndex, row, col, rowCount, colCount, textFileSaveFlags, rowDelimiter, columnDelimiter, cellDelimiter, encoding);
        }
        private void exportHtml(Exporter exporter, HtmlParam textParam, Stream stream)
        {
            int sheetIndex = -1;
            var sheetOnlyParam = textParam as SheetOnlyParam;
            if (sheetOnlyParam != null)
            {
                sheetIndex = sheetOnlyParam.sheetIndex.HasValue ? sheetOnlyParam.sheetIndex.Value : -1;
            }
            exporter.SaveHtml(stream, sheetIndex);
        }


        private static readonly Dictionary<ExportFileType, string> MimeMap
            = new Dictionary<ExportFileType, string>
            {
                {ExportFileType.Xlsx,"application/vnd.ms-excel"},
                {ExportFileType.Xls,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
                {ExportFileType.Csv,"text/plain"},
                {ExportFileType.Text, "application/octet-stream"},
                {ExportFileType.Pdf, "application/pdf"},
                {ExportFileType.Html, "text/html"}
            };
    }

    #endregion
    
    #region SpreadExportParm

    [JsonConverter(typeof(SpreadExportParamJsonConverter))]
    public class SpreadExportParam
    {
        private static Dictionary<ExportFileType, string> ExtensionMaps
            = new Dictionary<ExportFileType, string>
            {
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx, ".xlsx"},
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xls, ".xls"},
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Csv, ".csv"},
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Text, ".txt"},
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Pdf, ".pdf"},
                {GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Html, ".html"}
            };

        private static Dictionary<string, ExportFileType> FiletypeMaps
            = new Dictionary<string, ExportFileType>
            {
                {"xlsx", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx},
                {"xls", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xls},
                {"csv", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Csv},
                {"txt", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Text},
                {"pdf", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Pdf},
                {"html", GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Html}
            };

        internal JObject Spread
        {
            get;
            private set;
        }

        internal ExportFileType ActualExportFileType
        {
            get
            {
                if (ExportFileType != null)
                {
                    return ExportFileType ?? GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx;
                }
                else if (ExportFileName != null)
                {
                    return getFileType(ExportFileName) ?? GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx;
                }
                return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx;
            }
        }

        internal ExportFileType? getFileType(String fileName)
        {
            var suffix = Path.GetExtension(fileName);
            if (!String.IsNullOrEmpty(suffix))
            {
                suffix = suffix.ToLower();
            }
            switch (suffix)
            {
                case ".xlsx":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xlsx;
                case ".xls":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Xls;
                case ".pdf":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Pdf;
                case ".csv":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Csv;
                case ".text":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Text;
                case ".html":
                    return GrapeCity.Spread.Sheets.ExcelIO.ExportFileType.Html;
                default:
                    return null;
            }
        }

        internal String ActualExportFileName
        {
            get
            {
                var fileName = ExportFileName;
                var filePattern = "[/?*:;{}\\\\]+";
                if (String.IsNullOrEmpty(ExportFileName) ||
                    String.IsNullOrWhiteSpace(ExportFileName) ||
                    System.Text.RegularExpressions.Regex.IsMatch(ExportFileName, filePattern))
                {
                    fileName = "export";
                }
                if (ExportFileType == null)
                {
                    var type = getFileType(fileName);
                    return (type == null) ? (fileName + ExtensionMaps[ActualExportFileType]) : fileName;
                }
                else
                {
                    return fileName + ExtensionMaps[ActualExportFileType];
                }
            }
        }

        internal String ExportFileName
        {
            get;
            private set;
        }

        internal ExportFileType? ExportFileType
        {
            get;
            private set;
        }

        internal ExcelParam ExcelParam
        {
            get;
            private set;
        }

        internal CsvParam CsvParam
        {
            get;
            private set;
        }

        internal HtmlParam HtmlParam
        {
            get;
            private set;
        }

        internal PdfParam PdfParam
        {
            get;
            private set;
        }

        internal TextParam TextParam
        {
            get;
            private set;
        }

        internal class SpreadExportParamJsonConverter : JsonConverter
        {
            public override bool CanConvert(Type objectType)
            {
                return typeof(SpreadExportParam).IsAssignableFrom(objectType);
            }

            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                SpreadExportParam parm = new SpreadExportParam();
                while (reader.Read())
                {
                    if (reader.TokenType == JsonToken.PropertyName)
                    {
                        var propName = (string)reader.Value;

                        // read one more for property value
                        reader.Read();
                        var value = serializer.Deserialize(reader);
                        if (propName == "spread")
                        {
                            parm.Spread = value as JObject;
                        }
                        if (propName == "exportFileName")
                        {
                            parm.ExportFileName = Convert.ToString(value);
                        }
                        if (propName == "exportFileType")
                        {
                            parm.ExportFileType = FiletypeMaps[Convert.ToString(value)];
                        }
                        if (propName == "excel")
                        {
                            parm.ExcelParam = (value as JObject).ToObject<ExcelParam>();
                        }
                        if (propName == "csv")
                        {
                            parm.CsvParam = (value as JObject).ToObject<CsvParam>();
                        }
                        if (propName == "html")
                        {
                            parm.HtmlParam = (value as JObject).ToObject<HtmlParam>();
                        }
                        if (propName == "pdf")
                        {
                            parm.PdfParam = (value as JObject).ToObject<PdfParam>();
                        }
                        if (propName == "text")
                        {
                            parm.TextParam = (value as JObject).ToObject<TextParam>();
                        }
                    }
                }
                return parm;
            }



            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                throw new NotImplementedException();
            }
        }
    }

    internal class ExcelParam
    {
        public ExcelSaveFlags saveFlags;
        public string password;
    }

    internal class PdfParam
    {
        public int[] sheetIndexes;
        public PdfExportSettings setting;
    }

    internal abstract class SheetOnlyParam
    {
        public int? sheetIndex;
    }

    internal abstract class PlainTextParam : SheetOnlyParam
    {
        public TextFileSaveFlags saveFlags;
        public string encoding;
    }

    internal class CsvParam : PlainTextParam
    {
        public int? row;
        public int? column;
        public int? rowCount;
        public int? columnCount;
        public string rowDelimiter;
        public string columnDelimiter;
        public string cellDelimiter;
    }

    internal class TextParam : PlainTextParam
    {
        public int row;
        public int column;
        public int rowCount;
        public int columnCount;

        public string rowDelimiter;
        public string columnDelimiter;
        public string cellDelimiter;
    }

    internal class HtmlParam : SheetOnlyParam
    {
    }
    #endregion
}
